package com.lagou.edu.factory;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.annotation.Transactional;
import com.lagou.edu.utils.ClasspathPackageScanner;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 应癫
 * <p>
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */

    private static Map<String, Object> map = new HashMap<>();  // 存储对象

    private static class AutowiredInfo {
        // 需要注入的实例
        private Object obj;

        // 如果是方法注入，则该字段不为空
        private Method method;
        // 如果是属性注入，则该字段不为空
        private Field field;

        // 根据名称注入，则该字段不为空
        private String autowiredValue;

        // 根据类型注入，则该字段不为空
        private Class<?> autowiredType;

        public Object getObj() {
            return obj;
        }

        public void setObj(Object obj) {
            this.obj = obj;
        }

        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

        public Field getField() {
            return field;
        }

        public void setField(Field field) {
            this.field = field;
        }

        public String getAutowiredValue() {
            return autowiredValue;
        }

        public void setAutowiredValue(String autowiredValue) {
            this.autowiredValue = autowiredValue;
        }

        public Class<?> getAutowiredType() {
            return autowiredType;
        }

        public void setAutowiredType(Class<?> autowiredType) {
            this.autowiredType = autowiredType;
        }
    }

    private static class TransactionalInfo {
        private Object obj;
        private List<Method> methods;

        public TransactionalInfo addMethod(Method m) {
            if (methods == null) methods = new ArrayList<>();
            methods.add(m);
            return this;
        }

        public TransactionalInfo addMethods(List<Method> ms) {
            if (methods == null) methods = new ArrayList<>();
            Set<Method> noduplicatedMethods = new HashSet<>(ms);
            noduplicatedMethods.addAll(this.methods);
            this.methods = new ArrayList<>(noduplicatedMethods);
            return this;
        }

        public Object getObj() {
            return obj;
        }

        public void setObj(Object obj) {
            this.obj = obj;
        }

        public List<Method> getMethods() {
            return methods;
        }

        public void setMethods(List<Method> methods) {
            this.methods = methods;
        }
    }

    static {

        Map<String, AutowiredInfo> needAutowiredInstances = new HashMap<>();  // 等待注入属性的对象
        Map<String, TransactionalInfo> transactionalInstances = new HashMap<>();

        // 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
        // 加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> beanList = rootElement.selectNodes("//bean");
            for (int i = 0; i < beanList.size(); i++) {
                Element element = beanList.get(i);
                // 处理每个bean元素，获取到该元素的id 和 class 属性
                String id = element.attributeValue("id");        // accountDao
                String clazz = element.attributeValue("class");  // com.lagou.edu.dao.impl.JdbcAccountDaoImpl
                // 通过反射技术实例化对象
                Class<?> aClass = Class.forName(clazz);
                Object o = aClass.newInstance();  // 实例化之后的对象

                // 存储到map中待用
                map.put(id, o);

            }

            // 实例化完成之后维护对象的依赖关系，检查哪些对象需要传值进入，根据它的配置，我们传入相应的值
            // 有property子元素的bean就有传值需求
            List<Element> propertyList = rootElement.selectNodes("//property");
            // 解析property，获取父元素
            for (int i = 0; i < propertyList.size(); i++) {
                Element element = propertyList.get(i);   //<property name="AccountDao" ref="accountDao"></property>
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");

                // 找到当前需要被处理依赖关系的bean
                Element parent = element.getParent();

                // 调用父元素对象的反射功能
                String parentId = parent.attributeValue("id");
                Object parentObject = map.get(parentId);
                // 遍历父对象中的所有方法，找到"set" + name
                Method[] methods = parentObject.getClass().getMethods();
                for (int j = 0; j < methods.length; j++) {
                    Method method = methods[j];
                    if (method.getName().equalsIgnoreCase("set" + name)) {  // 该方法就是 setAccountDao(AccountDao accountDao)
                        method.invoke(parentObject, map.get(ref));
                    }
                }

                // 把处理之后的parentObject重新放到map中
                map.put(parentId, parentObject);

            }


            // 进行扫描方式生成bean
            // 支持 @Service  @Repository等
            // @Service、@Autowired、@Transactional
            Element packageScan = (Element) rootElement.selectSingleNode("//package-scan");
            if (packageScan != null) {
                String basePackages = packageScan.attributeValue("base-packages");
                List<String> pacakges =
                        Arrays.stream(basePackages.split(",")).map(String::trim).collect(Collectors.toList());
                List<String> fullyQualifiedClassNameList =
                        ClasspathPackageScanner.getFullyQualifiedClassNameList(pacakges, BeanFactory.class.getClassLoader());
                Set<String> nodepulicationClasses = new HashSet<>(fullyQualifiedClassNameList);
                for (String fullyQualifiedClassName : nodepulicationClasses) {
                    String shortClassName = fullyQualifiedClassName.contains(".") ?
                            fullyQualifiedClassName.substring(fullyQualifiedClassName.lastIndexOf(".")) : fullyQualifiedClassName;


                    Class<?> aClass = Class.forName(fullyQualifiedClassName);
                    // 查找带有@Service的类
                    Service annotation = aClass.getAnnotation(Service.class);
                    if (annotation != null) {

                        // 实例化该类
                        Object instance = aClass.newInstance();

                        String id = shortClassName;
                        String value = annotation.value();
                        if (!"".equals(value)) {
                            id = value;
                        }
                        if (map.containsKey(id)) {
                            throw new RuntimeException("存在重复声明的bean： " + id);
                        }


                        // 查找@Autowired注解
                        Method[] declaredMethods = aClass.getMethods();
                        for (Method declaredMethod : declaredMethods) {
                            Autowired autowiredAnno = declaredMethod.getAnnotation(Autowired.class);
                            if (autowiredAnno != null) {
                                String autowiredValue = autowiredAnno.value();
                                if (!"".equals(autowiredValue)) {
                                    Object needAutowiredObj = map.get(autowiredValue);
                                    if (needAutowiredObj == null) {
                                        //还没实例化好，则放入等待注入队列
                                        AutowiredInfo autowiredInfo = new AutowiredInfo();
                                        autowiredInfo.setObj(instance);
                                        autowiredInfo.setAutowiredValue(autowiredValue);
                                        autowiredInfo.setMethod(declaredMethod);
                                        needAutowiredInstances.put(id, autowiredInfo);
                                    } else {
                                        declaredMethod.setAccessible(true);
                                        // 注入
                                        declaredMethod.invoke(instance, needAutowiredObj);
                                    }
                                } else {
                                    Class<?> parameterType = declaredMethod.getParameterTypes()[0];
                                    int cnt = 0;
                                    for (Map.Entry<String, Object> entry : map.entrySet()) {
                                        if (cnt >= 2)
                                            throw new RuntimeException("根据类型找到多个能够注入进来的对象：" + parameterType.getName());
                                        if (entry.getValue().getClass() == parameterType ||
                                                entry.getValue().getClass().isAssignableFrom(parameterType)) {
                                            declaredMethod.setAccessible(true);
                                            // 注入
                                            declaredMethod.invoke(instance, entry.getValue());
                                            cnt += 1;
                                        }
                                    }
                                    if (cnt == 0) {
                                        // 暂时还没有找到，则放入等待注入队列
                                        AutowiredInfo autowiredInfo = new AutowiredInfo();
                                        autowiredInfo.setObj(instance);
                                        autowiredInfo.setAutowiredType(parameterType);
                                        autowiredInfo.setMethod(declaredMethod);
                                        needAutowiredInstances.put(id, autowiredInfo);
                                    }
                                }
                            }

                            // 处理方法上的@Transactional
                            Transactional tra = declaredMethod.getAnnotation(Transactional.class);
                            if (tra != null) {
                                TransactionalInfo transactionalInfo = new TransactionalInfo();
                                transactionalInfo.setObj(instance);
                                transactionalInstances.put(id,
                                        transactionalInstances.getOrDefault(id, transactionalInfo).addMethod(declaredMethod));
                            }
                        }

                        // 类上注解@Transactional
                        Transactional tra = aClass.getAnnotation(Transactional.class);
                        if (tra != null) {
                            Method[] methods = aClass.getMethods();
                            TransactionalInfo transactionalInfo = new TransactionalInfo();
                            transactionalInfo.setObj(instance);
                            transactionalInstances.put(id,
                                    transactionalInstances.getOrDefault(id, transactionalInfo).addMethods(Arrays.asList(methods)));
                        }

                        Field[] declaredFields = aClass.getDeclaredFields();
                        for (Field declaredField : declaredFields) {
                            Autowired autowiredAnno = declaredField.getAnnotation(Autowired.class);
                            if (autowiredAnno != null) {
                                String autowiredValue = autowiredAnno.value();
                                if (!"".equals(autowiredValue)) {
                                    Object needAutowiredObj = map.get(autowiredValue);
                                    if (needAutowiredObj == null) {
                                        // 还没实例化好，则放入等待注入队列
                                        AutowiredInfo autowiredInfo = new AutowiredInfo();
                                        autowiredInfo.setObj(instance);
                                        autowiredInfo.setAutowiredValue(autowiredValue);
                                        autowiredInfo.setField(declaredField);
                                        needAutowiredInstances.put(id, autowiredInfo);
                                    } else {
                                        // 注入
                                        declaredField.setAccessible(true);
                                        declaredField.set(instance, needAutowiredObj);
                                    }
                                } else {
                                    // 按照类型注入
                                    Class<?> type = declaredField.getType();
                                    int cnt = 0;
                                    for (Map.Entry<String, Object> entry : map.entrySet()) {
                                        if (cnt >= 2) throw new RuntimeException("根据类型找到多个能够注入进来的对象：" + type.getName());
                                        if (entry.getValue().getClass() == type ||
                                                entry.getValue().getClass().isAssignableFrom(type)) {
                                            declaredField.setAccessible(true);
                                            declaredField.set(instance, entry.getValue());
                                            cnt += 1;
                                        }
                                    }
                                    if (cnt == 0) {
                                        // 暂时还没有找到，则放入等待注入队列
                                        AutowiredInfo autowiredInfo = new AutowiredInfo();
                                        autowiredInfo.setObj(instance);
                                        autowiredInfo.setAutowiredType(type);
                                        autowiredInfo.setField(declaredField);
                                        needAutowiredInstances.put(id, autowiredInfo);
                                    }
                                }
                            }
                        }

                        map.put(id, instance);

                    }
                }

                // 对还没有注入的再次注入
                for (Map.Entry<String, AutowiredInfo> entry : needAutowiredInstances.entrySet()) {
                    String id = entry.getKey();
                    AutowiredInfo autowiredInfo = entry.getValue();
                    Object instance = autowiredInfo.getObj();
                    // 方法注入
                    Method method = autowiredInfo.getMethod();
                    if (method != null) {
                        String autowiredValue = autowiredInfo.getAutowiredValue();
                        if (autowiredValue != null) {
                            Object autowiredObj = map.get(autowiredValue);
                            if (autowiredObj == null) throw new RuntimeException("没有找到可以注入的对象：" + autowiredValue);
                            method.setAccessible(true);
                            method.invoke(instance, autowiredObj);
                        } else {
                            Class<?> autowiredType = autowiredInfo.getAutowiredType();
                            int cnt = 0;
                            for (Map.Entry<String, Object> e : map.entrySet()) {
                                if (cnt >= 2)
                                    throw new RuntimeException("根据类型找到多个能够注入进来的对象：" + autowiredType.getName());
                                if (e.getValue().getClass() == autowiredType ||
                                        e.getValue().getClass().isAssignableFrom(autowiredType)) {
                                    method.setAccessible(true);
                                    method.invoke(instance, e.getValue());
                                    cnt += 1;
                                }
                                if (cnt == 0) {
                                    throw new RuntimeException("根据类型找到多个能够注入进来的对象：" + autowiredType.getName());
                                }
                            }
                        }
                    } else {
                        Field field = autowiredInfo.getField();
                        String autowiredValue = autowiredInfo.getAutowiredValue();
                        if (autowiredValue != null) {
                            Object autowiredObj = map.get(autowiredValue);
                            if (autowiredObj == null) throw new RuntimeException("没有找到可以注入的对象：" + autowiredValue);
                            field.setAccessible(true);
                            field.set(instance, autowiredObj);
                        } else {
                            Class<?> autowiredType = autowiredInfo.getAutowiredType();
                            int cnt = 0;
                            for (Map.Entry<String, Object> e : map.entrySet()) {
                                if (cnt >= 2)
                                    throw new RuntimeException("根据类型找到多个能够注入进来的对象：" + autowiredType.getName());
                                if (e.getValue().getClass() == autowiredType ||
                                        e.getValue().getClass().isAssignableFrom(autowiredType)) {
                                    field.setAccessible(true);
                                    field.set(instance, e.getValue());
                                    cnt += 1;
                                }
                                if (cnt == 0) {
                                    throw new RuntimeException("根据类型找到多个能够注入进来的对象：" + autowiredType.getName());
                                }
                            }
                        }
                    }
                    map.put(id, instance);
                }

                // 处理@Transactional
                ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
                for (Map.Entry<String, TransactionalInfo> entry : transactionalInstances.entrySet()) {
                    String id = entry.getKey();
                    TransactionalInfo transactionalInfo = entry.getValue();
                    Object realObj = transactionalInfo.getObj();
                    Class<?>[] interfaces = realObj.getClass().getInterfaces();
                    if (interfaces != null && interfaces.length >= 1) {
                        Object proxy = proxyFactory.getJdkProxy(realObj);
                        map.put(id, proxy);
                    } else {
                        Object cglibProxy = proxyFactory.getCglibProxy(realObj);
                        map.put(id, cglibProxy);
                    }
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }


    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static Object getBean(String id) {
        return map.get(id);
    }

}
